<template>
    <view class="u-rate" :id="elId" ref="u-rate" :style="[$u.addStyle(customStyle)]">
        <view class="u-rate__content" @touchmove.stop="touchMove" @touchend.stop="touchEnd">
            <view class="u-rate__content__item" v-for="(item, index) in Number(count)" :key="index" :class="[elClass]">
                <view class="u-rate__content__item__icon-wrap" ref="u-rate__content__item__icon-wrap" @tap.stop="clickHandler($event, index + 1)">
                    <u-icon :name="
                            Math.floor(activeIndex) > index
                                ? activeIcon
                                : inactiveIcon
                        " :color="
                            disabled
                                ? '#c8c9cc'
                                : Math.floor(activeIndex) > index
                                ? activeColor
                                : inactiveColor
                        " :custom-style="{
                            padding: `0 ${$u.addUnit(gutter / 2)}`,
                        }" :size="size"></u-icon>
                </view>
                <view v-if="allowHalf" @tap.stop="clickHandler($event, index + 1)" class="u-rate__content__item__icon-wrap u-rate__content__item__icon-wrap--half" :style="[{
                        width: $u.addUnit(rateWidth / 2),
                    }]" ref="u-rate__content__item__icon-wrap">
                    <u-icon :name="
                            Math.ceil(activeIndex) > index
                                ? activeIcon
                                : inactiveIcon
                        " :color="
                            disabled
                                ? '#c8c9cc'
                                : Math.ceil(activeIndex) > index
                                ? activeColor
                                : inactiveColor
                        " :custom-style="{
                            padding: `0 ${$u.addUnit(gutter / 2)}`
                        }" :size="size"></u-icon>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
    import props from './props.js';

    // #ifdef APP-NVUE
    const dom = weex.requireModule("dom");
    // #endif
    /**
     * rate 评分
     * @description 该组件一般用于满意度调查，星型评分的场景
     * @tutorial https://www.uviewui.com/components/rate.html
     * @property {String | Number}	value			用于v-model双向绑定选中的星星数量 (默认 1 )
     * @property {String | Number}	count			最多可选的星星数量 （默认 5 ）
     * @property {Boolean}			disabled		是否禁止用户操作 （默认 false ）
     * @property {Boolean}			readonly		是否只读 （默认 false ）
     * @property {String | Number}	size			星星的大小，单位px （默认 18 ）
     * @property {String}			inactiveColor	未选中星星的颜色 （默认 '#b2b2b2' ）
     * @property {String}			activeColor		选中的星星颜色 （默认 '#FA3534' ）
     * @property {String | Number}	gutter			星星之间的距离 （默认 4 ）
     * @property {String | Number}	minCount		最少选中星星的个数 （默认 1 ）
     * @property {Boolean}			allowHalf		是否允许半星选择 （默认 false ）
     * @property {String}			activeIcon		选中时的图标名，只能为uView的内置图标 （默认 'star-fill' ）
     * @property {String}			inactiveIcon	未选中时的图标名，只能为uView的内置图标 （默认 'star' ）
     * @property {Boolean}			touchable		是否可以通过滑动手势选择评分 （默认 'true' ）
     * @property {Object}			customStyle		组件的样式，对象形式
     * @event {Function} change 选中的星星发生变化时触发
     * @example <u-rate :count="count" :value="2"></u-rate>
     */
    export default {
        name: "u-rate",
        mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
        data() {
            return {
                // 生成一个唯一id，否则一个页面多个评分组件，会造成冲突
                elId: uni.$u.guid(),
                elClass: uni.$u.guid(),
                rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离，用于滑动选择时计算距离
                activeIndex: this.value,
                rateWidth: 0, // 每个星星的宽度
                // 标识是否正在滑动，由于iOS事件上touch比click先触发，导致快速滑动结束后，接着触发click，导致事件混乱而出错
                moving: false,
            };
        },
        watch: {
            value(val) {
                this.activeIndex = val;
            },
            activeIndex: 'emitEvent'
        },
        methods: {
            init() {
                uni.$u.sleep().then(() => {
                    this.getRateItemRect();
                    this.getRateIconWrapRect();
                })
            },
            // 获取评分组件盒子的布局信息
            async getRateItemRect() {
                await uni.$u.sleep();
                // uView封装的获取节点的方法，详见文档
                // #ifndef APP-NVUE
                this.$uGetRect("#" + this.elId).then((res) => {
                    this.rateBoxLeft = res.left;
                });
                // #endif
                // #ifdef APP-NVUE
                dom.getComponentRect(this.$refs["u-rate"], (res) => {
                    this.rateBoxLeft = res.size.left;
                });
                // #endif
            },
            // 获取单个星星的尺寸
            getRateIconWrapRect() {
                // uView封装的获取节点的方法，详见文档
                // #ifndef APP-NVUE
                this.$uGetRect("." + this.elClass).then((res) => {
                    this.rateWidth = res.width;
                });
                // #endif
                // #ifdef APP-NVUE
                dom.getComponentRect(
                    this.$refs["u-rate__content__item__icon-wrap"][0],
                    (res) => {
                        this.rateWidth = res.size.width;
                    }
                );
                // #endif
            },
            // 手指滑动
            touchMove(e) {
                // 如果禁止通过手动滑动选择，返回
                if (!this.touchable) {
                    return;
                }
                this.preventEvent(e);
                const x = e.changedTouches[0].pageX;
                this.getActiveIndex(x);
            },
            // 停止滑动
            touchEnd(e) {
                // 如果禁止通过手动滑动选择，返回
                if (!this.touchable) {
                    return;
                }
                this.preventEvent(e);
                const x = e.changedTouches[0].pageX;
                this.getActiveIndex(x);
            },
            // 通过点击，直接选中
            clickHandler(e, index) {
                // ios上，moving状态取消事件触发
                if (uni.$u.os() === "ios" && this.moving) {
                    return;
                }
                this.preventEvent(e);
                let x = 0;
                // 点击时，在nvue上，无法获得点击的坐标，所以无法实现点击半星选择
                // #ifndef APP-NVUE
                x = e.changedTouches[0].pageX;
                // #endif
                // #ifdef APP-NVUE
                // nvue下，无法通过点击获得坐标信息，这里通过元素的位置尺寸值模拟坐标
                x = index * this.rateWidth + this.rateBoxLeft;
                // #endif
                this.getActiveIndex(x, true);
            },
            // 发出事件
            emitEvent() {
                // 发出change事件
                this.$emit("change", this.activeIndex);
                // 同时修改双向绑定的value的值
                this.$emit("input", this.activeIndex);
            },
            // 获取当前激活的评分图标
            getActiveIndex(x, isClick = false) {
                if (this.disabled || this.readonly) {
                    return;
                }
                // 判断当前操作的点的x坐标值，是否在允许的边界范围内
                const allRateWidth = this.rateWidth * this.count + this.rateBoxLeft;
                // 如果小于第一个图标的左边界，设置为最小值，如果大于所有图标的宽度，则设置为最大值
                x = uni.$u.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft
                // 滑动点相对于评分盒子左边的距离
                const distance = x;
                // 滑动的距离，相当于多少颗星星
                let index;
                // 判断是否允许半星
                if (this.allowHalf) {
                    index = Math.floor(distance / this.rateWidth);
                    // 取余，判断小数的区间范围
                    const decimal = distance % this.rateWidth;
                    if (decimal <= this.rateWidth / 2 && decimal > 0) {
                        index += 0.5;
                    } else if (decimal > this.rateWidth / 2) {
                        index++;
                    }
                } else {
                    index = Math.floor(distance / this.rateWidth);
                    // 取余，判断小数的区间范围
                    const decimal = distance % this.rateWidth;
                    // 非半星时，只有超过了图标的一半距离，才认为是选择了这颗星
                    if (isClick) {
                        if (decimal > 0) index++;
                    } else {
                        if (decimal > this.rateWidth / 2) index++;
                    }

                }
                this.activeIndex = Math.min(index, this.count);
                // 对最少颗星星的限制
                if (this.activeIndex < this.minCount) {
                    this.activeIndex = this.minCount;
                }

                // 设置延时为了让click事件在touchmove之前触发
                setTimeout(() => {
                    this.moving = true;
                }, 10);
                // 一定时间后，取消标识为移动中状态，是为了让click事件无效
                setTimeout(() => {
                    this.moving = false;
                }, 10);
            },
        },
        mounted() {
            this.init();
        },
    };
</script>

<style lang="scss" scoped>
    @import "../../libs/css/components.scss";
    $u-rate-margin: 0 !default;
    $u-rate-padding: 0 !default;
    $u-rate-item-icon-wrap-half-top: 0 !default;
    $u-rate-item-icon-wrap-half-left: 0 !default;

    .u-rate {
        @include flex;
        align-items: center;
        margin: $u-rate-margin;
        padding: $u-rate-padding;
        /* #ifndef APP-NVUE */
        touch-action: none;
        /* #endif */

        &__content {
            @include flex;

            &__item {
                position: relative;

                &__icon-wrap {
                    &--half {
                        position: absolute;
                        overflow: hidden;
                        top: $u-rate-item-icon-wrap-half-top;
                        left: $u-rate-item-icon-wrap-half-left;
                    }
                }
            }
        }
    }

    .u-icon {
        /* #ifndef APP-NVUE */
        box-sizing: border-box;
        /* #endif */
    }
</style>